import { mergeMap as _observableMergeMap, catchError as _observableCatch } from 'rxjs/operators';
import { Observable, from as _observableFrom, throwError as _observableThrow, of as _observableOf } from 'rxjs';
import { Inject, Optional, InjectionToken } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams, HttpResponse, HttpResponseBase, HttpErrorResponse } from '@angular/common/http';
import { throwException } from '../utils/exception';
import { blobToText } from '../utils/blob2text';

export const API_BASE_URL = new InjectionToken<string>('API_BASE_URL');

export class Client {
  protected http: HttpClient;
  protected baseUrl: string;
  protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

  constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) {
    this.http = http;
    this.baseUrl = baseUrl ? baseUrl : '/api';
  }

  private precessResp: object = {
    get: this.processGetResp,
    post: this.processPostResp,
    put: this.processPutResp,
    delete: this.processDeleteResp
  };

  protected getUrl(urlPrefix: string) {
    return this.baseUrl + urlPrefix;
  }

  protected getOptions(vm?: any) {
    const options: any = {
      observe: 'response',
      responseType: 'blob',
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      })
    };
    if (vm) {
      const content_ = JSON.stringify(vm);
      options.body = content_;
    }
    return options;
  }

  protected request<T>(method: string, url: string, options_: object, Vm: any): any {
    const url_ = url.replace(/[?&]$/, '');
    return this.http.request(method, url_, options_).pipe(_observableMergeMap((response_: any) => {
      return this.precessResp[method](response_, Vm);
    })).pipe(_observableCatch((response_: any) => {
      if (response_ instanceof HttpResponseBase) {
        try {
          return this.precessResp[method](response_, Vm);
        } catch (e) {
          return <Observable<T>><any>_observableThrow(e);
        }
      } else {
        return <Observable<T>><any>_observableThrow(response_);
      }
    }));
  }

  private processPostResp<T>(response: HttpResponseBase, Vm: any): Observable<T> {
    const status = response.status;
    const responseBlob =
      response instanceof HttpResponse ? response.body :
        (<any>response).error instanceof Blob ? (<any>response).error : undefined;

    const _headers: any = {};
    if (response.headers) { for (const key of response.headers.keys()) { _headers[key] = response.headers.get(key); } }
    if (status === 201) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result201: any = null;
        const resultData201 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result201 = resultData201 ? Vm.fromJS(resultData201) : new Vm();
        return _observableOf(result201);
      }));
    } else if (status === 400) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result400: any = null;
        const resultData400 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result400 = resultData400 ? ApiException.fromJS(resultData400) : new ApiException();
        return throwException('A server error occurred.', status, _responseText, _headers, result400);
      }));
    } else if (status !== 200 && status !== 204) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        return throwException('An unexpected server error occurred.', status, _responseText, _headers);
      }));
    }
    return _observableOf<T>(<any>null);
  }

  private processGetResp<T>(response: HttpResponseBase, Vm: any): Observable<T[]> {
    const status = response.status;
    const responseBlob =
      response instanceof HttpResponse ? response.body :
        (<any>response).error instanceof Blob ? (<any>response).error : undefined;

    const _headers: any = {};
    if (response.headers) { for (const key of response.headers.keys()) { _headers[key] = response.headers.get(key); } }
    if (status === 200) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result200: any = null;
        const resultData200 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        if (resultData200 && resultData200.constructor === Array) {
          result200 = [];
          for (const item of resultData200) {
            result200.push(Vm.fromJS(item));
          }
        }
        return _observableOf(result200);
      }));
    } else if (status === 400) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result400: any = null;
        const resultData400 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result400 = resultData400 ? ApiException.fromJS(resultData400) : new ApiException();
        return throwException('A server error occurred.', status, _responseText, _headers, result400);
      }));
    } else if (status !== 200 && status !== 204) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        return throwException('An unexpected server error occurred.', status, _responseText, _headers);
      }));
    }
    return _observableOf<T[]>(<any>null);
  }

  private processPutResp<T>(response: HttpResponseBase, Vm: any): Observable<T> {
    const status = response.status;
    const responseBlob =
      response instanceof HttpResponse ? response.body :
        (<any>response).error instanceof Blob ? (<any>response).error : undefined;

    const _headers: any = {};
    if (response.headers) { for (const key of response.headers.keys()) { _headers[key] = response.headers.get(key); } }
    if (status === 200) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result200: any = null;
        const resultData200 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result200 = resultData200 ? Vm.fromJS(resultData200) : new Vm();
        return _observableOf(result200);
      }));
    } else if (status === 400) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result400: any = null;
        const resultData400 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result400 = resultData400 ? ApiException.fromJS(resultData400) : new ApiException();
        return throwException('A server error occurred.', status, _responseText, _headers, result400);
      }));
    } else if (status !== 200 && status !== 204) {
      return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        return throwException('An unexpected server error occurred.', status, _responseText, _headers);
      }));
    }
    return _observableOf<T>(<any>null);
  }


  private processDeleteResp<T>(response: HttpResponseBase, Vm: any): Observable<T> {
    const status = response.status;
    const responseBlob =
        response instanceof HttpResponse ? response.body :
        (<any>response).error instanceof Blob ? (<any>response).error : undefined;

    const _headers: any = {};
    if (response.headers) { for (const key of response.headers.keys()) { _headers[key] = response.headers.get(key); } }
    if (status === 200) {
        return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result200: any = null;
        const resultData200 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result200 = resultData200 ? Vm.fromJS(resultData200) : new Vm();
        return _observableOf(result200);
        }));
    } else if (status === 400) {
        return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        let result400: any = null;
        const resultData400 = _responseText === '' ? null : JSON.parse(_responseText, this.jsonParseReviver);
        result400 = resultData400 ? ApiException.fromJS(resultData400) : new ApiException();
        return throwException('A server error occurred.', status, _responseText, _headers, result400);
        }));
    } else if (status !== 200 && status !== 204) {
        return blobToText(responseBlob).pipe(_observableMergeMap(_responseText => {
        return throwException('An unexpected server error occurred.', status, _responseText, _headers);
        }));
    }
    return _observableOf<T>(<any>null);
  }
}

export interface IApiException {
  statusCode?: number | null;
  message?: string | null;
  status?: string | null;
  error?: string | null;
  errors?: any | null;
  timestamp?: string | null;
  path?: string | null;
}

export class ApiException implements IApiException {

  constructor(data?: IApiException) {
      if (data) {
          for (const property in data) {
              if (data.hasOwnProperty(property)) {
                  (<any>this)[property] = (<any>data)[property];
              }
          }
      }
  }
  statusCode?: number | null;
  message?: string | null;
  status?: string | null;
  error?: string | null;
  errors?: any | null;
  timestamp?: string | null;
  path?: string | null;

  static fromJS(data: any): ApiException {
      data = typeof data === 'object' ? data : {};
      const result = new ApiException();
      result.init(data);
      return result;
  }

  init(data?: any) {
      if (data) {
          this.statusCode = data['statusCode'] !== undefined ? data['statusCode'] : <any>null;
          this.message = data['message'] !== undefined ? data['message'] : <any>null;
          this.status = data['status'] !== undefined ? data['status'] : <any>null;
          this.error = data['error'] !== undefined ? data['error'] : <any>null;
          if (data['errors']) {
              this.errors = {};
              for (const key in data['errors']) {
                  if (data['errors'].hasOwnProperty(key)) {
                      this.errors[key] = data['errors'][key];
                  }
              }
          }
          this.timestamp = data['timestamp'] !== undefined ? data['timestamp'] : <any>null;
          this.path = data['path'] !== undefined ? data['path'] : <any>null;
      }
  }

  toJSON(data?: any) {
      data = typeof data === 'object' ? data : {};
      data['statusCode'] = this.statusCode !== undefined ? this.statusCode : <any>null;
      data['message'] = this.message !== undefined ? this.message : <any>null;
      data['status'] = this.status !== undefined ? this.status : <any>null;
      data['error'] = this.error !== undefined ? this.error : <any>null;
      if (this.errors) {
          data['errors'] = {};
          for (const key in this.errors) {
              if (this.errors.hasOwnProperty(key)) {
                  data['errors'][key] = this.errors[key] !== undefined ? this.errors[key] : <any>null;
              }
          }
      }
      data['timestamp'] = this.timestamp !== undefined ? this.timestamp : <any>null;
      data['path'] = this.path !== undefined ? this.path : <any>null;
      return data;
  }
}
